package rtcsession

import (
	"bufio"
	"fmt"
	"gitee.com/general252/gortc/pkg/rtprtcp"
	"gitee.com/general252/gortc/pkg/track"
	"log"
	"net"

	"gitee.com/general252/gortc/pkg/rtcsdp"
	"github.com/pion/rtcp"
	"github.com/pion/rtp"
)

var _ track.RtpRtcpWriter = (*RtcConnection)(nil)

type RtcConnection struct {
	reader track.RtpRtcpReader
	conn   net.Conn

	overTCP *rtprtcp.RtpOverTCP
}

func NewRtcConnection(reader track.RtpRtcpReader) *RtcConnection {
	tis := &RtcConnection{
		reader: reader,
	}

	return tis
}

func (tis *RtcConnection) Close(cause error) {
	if tis.conn != nil {
		_ = tis.conn.Close()
	}
	if tis.reader != nil {
		tis.reader.Close(cause)
	}
}

func (tis *RtcConnection) WriteRTP(pkt *rtp.Packet, addr net.Addr) error {
	if tis.conn == nil {
		return fmt.Errorf("no connection")
	}

	data, err := pkt.Marshal()
	if err != nil {
		return err
	}

	if tis.overTCP != nil {
		_, err = tis.overTCP.WriteRtpOverTCP(tis.conn, data)
	} else {
		_, err = tis.conn.Write(data)
	}

	return err
}

func (tis *RtcConnection) WriteRTCP(pkt []rtcp.Packet, addr net.Addr) error {
	if tis.conn == nil {
		return fmt.Errorf("no connection")
	}

	data, err := rtcp.Marshal(pkt)
	if err != nil {
		return err
	}

	if tis.overTCP != nil {
		_, err = tis.overTCP.WriteRtpOverTCP(tis.conn, data)
	} else {
		_, err = tis.conn.Write(data)
	}

	return err
}

func (tis *RtcConnection) Dial(objectAnswer *rtcsdp.Session) error {
	var conn net.Conn

	for _, m := range objectAnswer.Medias {
		if conn != nil {
			break
		}

		for _, candidate := range m.Candidates {
			if conn != nil {
				break
			}

			switch candidate.NetworkType {
			case "udp4", "udp":
				if c, err := net.DialUDP(candidate.NetworkType, nil, &net.UDPAddr{
					IP:   net.ParseIP(candidate.Address),
					Port: candidate.Port,
				}); err != nil {
					log.Println(err)
				} else {
					conn = c
					_ = c.SetWriteBuffer(512 * 1024)
					_ = c.SetReadBuffer(512 * 1024)
				}
			case "tcp4", "tcp":
				if c, err := net.DialTCP(candidate.NetworkType, nil, &net.TCPAddr{
					IP:   net.ParseIP(candidate.Address),
					Port: candidate.Port,
				}); err == nil {
					conn = c
					tis.overTCP = rtprtcp.NewRtpOverTCP()
					_ = c.SetWriteBuffer(512 * 1024)
					_ = c.SetReadBuffer(512 * 1024)
				}
			}
		}
	}

	if conn == nil {
		return fmt.Errorf("not connect")
	}

	tis.conn = conn

	go func() {
		if tis.overTCP != nil {
			tis.readLoopTCP(conn.(*net.TCPConn))
		} else {
			tis.readLoopUDP(conn.(*net.UDPConn))
		}
	}()

	return nil
}

func (tis *RtcConnection) readLoopUDP(conn *net.UDPConn) {

	buff := make([]byte, 2048)
	reader := tis.reader

	defer func() {
		tis.Close(fmt.Errorf("udp connection closed"))
	}()

	for {
		n, addr, err := conn.ReadFromUDP(buff)
		if err != nil {
			log.Println(err)
			return
		}
		if n <= 0 {
			continue
		}
		data := buff[:n]
		_ = addr
		_ = data

		rtcpPackets, rtpPks, err := reader.ParsePacket(data)
		if err != nil {
			log.Println(err)
			continue
		}

		if len(rtcpPackets) > 0 {
			reader.ReadPacketRTCP(rtcpPackets, addr)
			for _, pkt := range rtcpPackets {
				log.Printf("rtcp: %v", pkt)
			}
		}

		if rtpPks != nil {
			//log.Printf("rtp: %v", rtpPks.SSRC)
			reader.ReadPacketRTP(rtpPks, addr)
		}
	}

}

func (tis *RtcConnection) readLoopTCP(conn *net.TCPConn) {
	var (
		addr   = conn.RemoteAddr()
		reader = tis.reader
		h      = rtprtcp.NewRtpOverTCP()
		r      = bufio.NewReader(conn)
	)

	defer func() {
		tis.Close(fmt.Errorf("tcp connection closed"))
	}()

	for {
		data, err := h.ReadRtpOverTCP(r)
		if err != nil {
			log.Println(err)
			return
		}

		rtcpPackets, rtpPks, err := reader.ParsePacket(data)
		if err != nil {
			log.Println(err)
			continue
		}

		if len(rtcpPackets) > 0 {
			reader.ReadPacketRTCP(rtcpPackets, addr)
			for _, pkt := range rtcpPackets {
				log.Printf("rtcp: %v", pkt)
			}
		}

		if rtpPks != nil {
			//log.Printf("rtp: %v", rtpPks.SSRC)
			reader.ReadPacketRTP(rtpPks, addr)
		}
	}
}
